-- SyncM
-- Create by weism
-- 客户端与服务器的消息同步管理

module("SyncM", package.seeall);

-- 当前正在同步的消息
local sync, messages = 0, {};

-- 同步锁
local locked = false;

-- 最近一次同步的时间
local lastTime = -999;

-- 尚未开始同步的消息列表
local todoMessages = {};

-- 最近游戏的玩家
local rid;

-- 内部函数声明
local restore;

-- 模块初始化
function init()
    EventMgr.removeAll("SyncM");
    EventMgr.register("SyncM", event.LOGIN_OK, function()
        -- 登录成功后，需要还原出本地的数据，并请求与服务器进行同步
        rid = ME.user.rid;
        lastTime = -999;
        messages = {};
        todoMessages = {};
        restore();
    end);
end

-- 更新sync值
function updateSync(syncNo)
    sync = syncNo;
end

-- 添加一条同步消息
function addMessage(cmd, para)
    table.insert(todoMessages, { cmd, para });
end

-- 当前是否需要进行同步
function needSync()
    return #todoMessages > 0;
end

-- 判断能否同步
function canSync()
    if (not Socket.isConnected()) then
        cclog("没有连接上服务器，不能进行同步。");

        return false;
    end

    if (not ME.isInGame) then
        cclog("玩家不在游戏中，不能进行同步。");
        return false;
    end

    -- 是否被锁住了（上一次同步还没有收到回复）
    if locked then
        cclog("上一次同步没有等到回复。");
        return false;
    end

    local socket = require "socket";
    if (socket.gettime() - lastTime < 30) then
        cclog("正在同步中，30s内不适合再次发起。lastTime:"..lastTime.." time:"..socket.gettime().." sub:"..(socket.gettime() - lastTime));
        return false;
    end

    cclog("开始同步。");

    return true;
end

-- 解锁
function reloginUnlock(serverSync)
    -- 两边一样的，并且被锁住，应该是掉线被锁
    -- 尝试解锁并且主动同步一次
    if sync == serverSync and locked then
        locked = false;
        lastTime = -999;
        startSync();
    end
end

-- 开始进行同步
function startSync()
    -- 判断
    if not canSync() then
        return;
    end

    local socket = require "socket";
    lastTime = socket.gettime();

    -- 将todoMessages的消息内容剪切出来
    messages = table.append(messages, todoMessages);
    todoMessages = {};
    if (#messages == 0) then
        -- 没有任何数据需要同步
        lastTime = -999;
        return;
    end

    -- 先锁住
    locked = true;

    -- 发送消息给服务器，开始同步
    trace("SyncM", "开始进行同步，版本号(%d)，消息数量：%d", sync, #messages);
    Communicate.send("CMD_START_SYNC", { sync = sync });
    for _, v in ipairs(messages) do
        Communicate.send(v[1], v[2]);
    end
    Communicate.send("CMD_END_SYNC", { sync = sync });
end

-- 同步完毕
function endSync(success)
    success = iif(success == nil, true, success);
    trace("SyncM", "同步完成，当前同步版本号：%d", ME.user.dbase:query("sync", 0));
    sync = ME.user.dbase:query("sync", 0);
    messages = {};
    if (not success) then
        -- 同步失败了后续的东西也不需要再同步了，直接丢弃以服务器为准
        todoMessages = {};
    end

    lastTime = -999;

    -- 解锁
    locked = false;

    save();
end

-- 确认正常同步完毕了
function assureEndSync(syncNo)
    if syncNo > sync then
        -- 之前的同步没有成功
        ME.user.dbase:set("sync", syncNo);

        endSync(true);
    end

    -- 回应服务器
    Operation.cmd_ack_sync(syncNo);

    startSync();
end

-- 保存操作
function save()
    if (rid == "" or rid == nil) then return end

    -- 打开
    local path = cc.FileUtils:getInstance():getWritablePath();
    local f = io.open(path .. rid .. ".txt", "w");

    -- 拼接内容
    local m = {
        sync = sync,
        messages = Array.new(messages),
        todoMessages = Array.new(todoMessages),
    };
    local content = LPC.saveString(m);

    -- 写入文件
    f:write(content);
    f:close()
end

-- 还原操作
restore = function()
    -- 解锁
    locked = false;

    -- 打开
    local path = cc.FileUtils:getInstance():getWritablePath();
    local f = io.open(path .. rid .. ".txt", "r");

    -- 本地没有该文件
    if f == nil then
        return;
    end

    local content = f:read("*all");
    f:close()
    -- 本地没有数据
    if not content or content == "" then
        return;
    end

    m = LPC.restoreString(content);
    if (m.sync ~= sync) then
        cclog("本地数据出现异常，自动以服务器数据为准。");
        sync = ME.user.dbase:query("sync", 0);
        messages = {};
        todoMessages = {};
        SyncM.save();
    else
        todoMessages = table.append(m.messages, m.todoMessages);
        SyncM.startSync();
    end
end
